Skip to content

skills(pm-dispatch): the single-writer set is what SINGLE_CLAIM_PATHS enumerates — said where the seat reads batch independence - #17089

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-17032-single-claim-set-is-enumerated
Sep 9, 2026
Merged

skills(pm-dispatch): the single-writer set is what SINGLE_CLAIM_PATHS enumerates — said where the seat reads batch independence#17089
os-zhuang merged 2 commits into
mainfrom
claude/issue-17032-single-claim-set-is-enumerated

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #17032

What this changes

.claude/skills/pm-dispatch/SKILL.md, the 候选与批次 block, lines 438–442 rewritten in place: 812/812 lines, every touched line ≤120 bytes. Direction (1) of the card — say it once, where a dispatching seat reads about batch independence. Directions (2) (the check's display name) and (3) (the gate's passing output) belong to the gate's own lane: scripts/check-single-claim-paths.mjs and the workflow are untouched. references/core-rules.md is untouched too (151/151): its clause states independence by constructed file surface and never used the word; the reader who needs the mechanism is on SKILL.md.

Before (854639b3, :438–442):

- 并行度以 `batch` 封顶;同批独立性按文件面不相交判,⛔ 不按包;`priority:p0` 可超 `batch`。
- 第 N 单派发前读 `scripts/pm/os-verify-lock.sh --status`:到达深度 ≥ `LOCK_DEPTH_HOLD`(= 2)即等。
- 到达深度 = `queue N:` 行数 + 1(待派 dev 的运行算作到达);`state:` holder 与 `parked` 行不计。
- 有效上限是锁宽的函数,⛔ 不是第二个 `batch`;`priority:p0` 可超 `batch`,⛔ 不越过深度等待。
- 阈值出处是深度表:深度 1/2/3/4 的 exit 99 率 1%/23%/36%/56%;等待不填槽,读数带 UTC 写进认领。

After (9bbd015cb, :438–442; widths 118 / 114 / 112 / 114 / 118 bytes, measured with printf '%s' LINE | wc -c):

- 并行度以 `batch` 封顶,等待不填槽;同批独立按文件面不相交判,⛔ 不按包、不按 check 名。
- single-writer 路径 = `SINGLE_CLAIM_PATHS` 所枚举;共享其它路径是普通并发,后落地方解冲突。
- 第 N 单派发前读 `scripts/pm/os-verify-lock.sh --status`:到达深度 ≥ `LOCK_DEPTH_HOLD`(= 2)即等。
- 到达深度 = `queue N:` 行数 + 1(待派 dev 的运行算作到达);`state:` holder 与 `parked` 行不计。
- 有效上限是锁宽的函数,⛔ 不是第二个 `batch`;`priority:p0` 可超 `batch`,⛔ 不越过深度等待。

Gloss of the two rewritten lines: parallelism is capped by batch and a wait does not fill a slot; same-batch independence is judged by disjoint file surfaces — not by package, and not by a check's name. The single-writer paths are exactly what SINGLE_CLAIM_PATHS enumerates (no count hardcoded — the script is the authority and a number would rot); two open PRs sharing any other path is ordinary concurrent work, and the cost is one merge resolution paid by whichever lands second.

Paid in place — the three deletions and where each lives instead

deleted from :438–442 why it is not a loss
priority:p0 可超 batch (at :438) the line three below it (:441, now :442) states it verbatim, with its qualifier ⛔ 不越过深度等待 — a byte-level duplicate.
读数带 UTC 写进认领 (at :442) :163 板面/树/队列读数恒带 UTC 取数时刻;认领、派发令、复核、轮报与座位贴皆同 already binds the lock reading in the claim; references/core-rules.md :47 says it again.
阈值出处是深度表:深度 1/2/3/4 的 exit 99 率 1%/23%/36%/56% (at :442) the one real content deletion. It is the provenance of LOCK_DEPTH_HOLD = 2, not an operative rule; the rule (:440 depth ≥ 2 ⇒ wait) stays. The file's own charter, quoted by the ratchet's verdict text: provenance is one line, stories live on cards, not in operational text — the table now lives in the commit that landed it, 21e36a2d3 (PR #16271), reachable by git log -S'23%/36%/56%' -- .claude/skills/pm-dispatch/SKILL.md. Reviewer's call; see 风险 below.

等待不填槽 was moved, not deleted: from :442 to :438, beside 并行度以 batch 封顶 — the same rule at the slot it qualifies.

The sweep the card asked for — done first

Channel: repo-scoped REST (probe GET /repos/objectstack-ai/objectstack/issues/17032 → 200; /rate_limit reads the 15000/h form), then GET /repos/objectstack-ai/objectstack/issues/comments?since=…&per_page=100&page=N&sort=created&direction=desc paged to a short page — /search/* was not used (MCP calls for the whole run: 0). Local regex single[- ]writer, case-insensitive, then classified by hand from each body's own seat markers (session id / lane / seat post).

Classification of the 122 (window B):

  • ~60 neutral: the check's display name quoted inside a green-check roster of a contract review / ACCEPT / adoption record. Not the model.
  • ~6 the lane-charter sense (scripts/pm/** single writer is the objectstack seat; "single writer of the seat post"). A different subject.
  • The over-read model — "any file two open PRs both change is a single-writer path the gate enforces" — acted on, by seat:
lane session when (UTC) what it did on that basis
domain:devx (no id in body) 09-05 18:48–20:05, #15993 · #15985 · #15948 "scripts/check-regen-pending.mjs is a single-writer path, and No other open PR may claim the same single-writer path is a required check" — a repair deliberately NOT ported on that ground, three cards
domain:services (seat post #6021) 09-05 18:54, #15981 "a PR opened for this card today would take approval-service.ts while …" under "a required check"
domain:cli session_01D47qPfEWVPmhguWgBZCi5N 09-06 04:49 #15620 · 11:19 #16091 · 19:37 #16337 · 19:38 #16114 · 09-07 02:49 #15907 "rest-server.ts … the single-writer lock on that file"; "CI enforces it"; "reserved single-writer path, held for #15405 and enforced by CI"; parallel dispatch declined on that ground
domain:engine session_01ARYe3yQTQCUFm5qPYNgKaJ 09-06 12:22, #16159 "both PRs edit the barrel packages/objectql/src/index.ts and the repo runs a single-writer-path check"
domain:services session_012zTkyNHJ7TkuN2oXtP5x37 09-08 09:50 → 09-09 03:00 the card's own instance: thirteen claims, the #16919 block, the harness.ts fence
domain:engine session_01ADLdAs2pVcH17h9tZKWMBg 09-09 00:03, #16344 card not dispatched: "the third writer on that file, against the repo's own No other open PR may claim the same single-writer path gate"
domain:cli session_015QE8qk46e5CHJxyQEUjbf8 09-09 05:11 #15405 · 06:10 #16813 "rest-server.ts is a single-writer hot file"; "pnpm-lock.yaml is a generated single-writer path … check for a competing holder before editing it" — both after the 03:06 retraction
isolated review seat (no id in body) 09-08 15:00, #16863 accepted "single-writer claim on package.json by #16805" as "true against the tree"; F2: "under single-writer this PR cannot enter the queue until #16805 lands"

Verdict: lane-wide, not personal — four dispatching lanes (services, cli, engine, devx) plus one review seat, at least twelve distinct comments acting on the model across four days, and the phrase's only in-repo source before this PR was the check's display name (grep -n -i single-writer .claude/skills/ on 854639b3: 0 hits; after: 1, SKILL.md:439; control word batch: 7 before, 7 after).

Reverse verification — the ratchet reads this file

On 9bbd015cb (fix committed first): one line appended → on-disk marker count 1, wc -l 813 → node scripts/pm/check-skill-line-ratchet.mjs exit 1 with its own verdict line ✗ check-skill-line-ratchet: .claude/skills/pm-dispatch/SKILL.md is 813 lines; the ratchet ceiling is 812. Restored with git checkout HEAD -- PATH (trap-guarded, absolute path): marker count 0, 812 lines, git diff HEAD empty, git status --porcelain empty, working-tree blob hash 5e0b353e71d48de445714fd5987043dfa23b3b17 equal to HEAD:PATH, ratchet exit 0 again. The hash equality is the proof; the trap is only the crash path.

Gates — all on 9bbd015cb, exit captured before any pipe

Derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths passed; 16 commands) and reconciled with --ran: ✓ dispatch-gates --ran: 16 derived famil(ies) accounted for — 16 run, 0 NOT-MEASURED.

command exit the gate's own verdict line
pnpm check:pm-skill-ratchet 0 ✓ … SKILL.md is 812 lines (ceiling 812; headroom 0) · core-rules.md is 151 lines (ceiling 151; headroom 0)
pnpm check:pm-skill-id-lint 0 ✓ check-skill-id-lint: 26 file(s) clean (pattern /#[0-9]{3,}/g)
pnpm check:skill-frame-sync 0 ✓ check-skill-frame-sync: 2 copies of the decision frame are structurally isomorphic across 2 files
pnpm check:pm-governed-merges 0 ✓ check-governed-merges --self-test: 274 assertions …
pnpm check:nul-bytes 0 check-nul-bytes: OK (scanned 8021 text file(s) … no raw ASCII control bytes)
pnpm check:doc-authoring 0 ✓ doc authoring guard: 398 files clean — no bare metadata literals
pnpm check:pm-governed-prose 0 ✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces
node scripts/check-closing-keyword-parity.mjs (+ --self-test) 0 / 0 OK (3 parsers agree on all 9 keywords …)
node scripts/check-comment-mask-corpus.mjs 0 ✓ comment-mask corpus sweep: 6410 files, 0 disagree
node scripts/pm/check-governed-queue-guard.mjs --self-test 0 ✓ … 144 cases pass
pnpm check:agent-test-spelling · check:driver-memory-census · check:refd-timer-probe · check:watch-hint-literal 0 each each gate's OK line in the run log
pnpm --filter @objectstack/lint run check:doc-formula-expressions 0 after pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint under the verify lock (4/4 turbo cache hits, lock held 1 s); first run exited 3 = the gate's own PREREQUISITE NOT MET (unbuilt @objectstack/formula), NOT MEASURED, not a red. Verdict: ✓ check:doc-formula-expressions self-test: 58 cases passed

Governed predicate, copied from node scripts/pm/check-governed-merges.mjs --test .claude/skills/pm-dispatch/SKILL.md .claude/skills/pm-dispatch/references/core-rules.md (exit 3):

governed-surface predicate: 2 of 2 path(s) hit the register (5 surfaces, repo-agnostic).
  ⛔  GOVERNED — a human merge is the review record for this PR (#9495 regime).
      No seat flips it ready, enqueues it, or arms auto-merge (AGENTS.md Prime Directive #14).
      .claude/** ×2 — the agent instruction tree (skills, agents, hooks, settings)

⇒ draft, human merge. No seat flips it ready, queues it, arms auto-merge, or approves it. skip-changeset: .claude/** ships in no package's files[] (fast lane, not published). Clause-②: no per the claim.

Deviations from the dispatch, stated

  • The dispatch named the script path as the thing to cite; the line cites SINGLE_CLAIM_PATHS — the exported enumeration itself. The path is 38 bytes and no ≤120-byte spelling carried the path plus both halves of the rule (the closest, single-writer 只有 \scripts/check-single-claim-paths.mjs` 枚举的路径;其余共享路径是普通并发,后落地方解冲突。, is 136 bytes); git grep SINGLE_CLAIM_PATHSlands on the array inscripts/check-single-claim-paths.mjs`. The count is not hardcoded (所枚举), per the dispatch's mechanism assumption 2.
  • The new rule needed two lines, not one; three deletions inside :438–442 paid them (table above). Two are duplicates; the depth-table provenance is not.
  • The sweep window was widened to 09-05 in a second pass (the card asked for "recent"; the two-day pass alone could not answer personal-vs-lane-wide).
  • The dispatch's suggested grep control word single-claim occurs nowhere in .claude/skills/** on the tree, before or after — it is not a control; batch (7 hits) was used instead.

验收备注 (noted, not filed)

维护者速读(草稿)

改了什么

.claude/skills/pm-dispatch/SKILL.md 候选与批次段 :438–442 原地改写,总行数不变(812/812)、每行 ≤120 字节。新增两条规则:同批独立性按文件面判、⛔ 不按包也不按 check 名;single-writer 路径只有 SINGLE_CLAIM_PATHS 枚举的那几条,两个 open PR 共享其它任何路径都是普通并发,代价是后落地的一方解一次冲突。为付行数,删掉了同段内两处重复(priority:p0 可超 batch 与 :441 逐字重复;读数带 UTC 写进认领与 :163 重复)和一处出处数据(LOCK_DEPTH_HOLD = 2 的深度表)。等待不填槽 只是挪了位置。门禁脚本与 workflow 一字未动。

为什么改

四天里四个车道的派发席(services、cli、engine、devx)加一个复核席,把 CI 检查名 No other open PR may claim the same single-writer path 读成了「任何两个 open PR 共同改的文件都是单写手路径」,据此挡了本可派发的卡、给在跑的 dev 划了不该划的围栏、拒绝了本可顺带的修复。三个席位各自实测证伪过一次,但每次都停留在该席位的评论里,下一个会话又把错误模型捡回来。仓库里除了检查名,没有任何一处写着这条规则;本 PR 把它写在派发席判同批独立性时必读的那几行上。

风险与代价(含回滚)

  • 风险一:删掉的深度表(深度 1/2/3/4 对应 exit 99 率 1%/23%/36%/56%)是 LOCK_DEPTH_HOLD = 2 的唯一树内出处;规则本身(深度 ≥ 2 即等)保留,出处退回落地它的提交 21e36a2d3(docs(pm-dispatch): read the verify-lock queue depth before each dispatch and hold at depth ≥ 2 (#14944 Q1 B) #16271)。若维护者认为出处必须留在文件里,替代方案是不写「不按 check 名」这半句并把两条新规则压成一条(信息损失在新规则一侧),或按棘轮头部的跨文件搬迁条款另立单处理。
  • 风险二:行内引用的是导出常量 SINGLE_CLAIM_PATHS 而非脚本路径(路径 38 字节装不下);常量重命名会让这行失锚,与引用路径的风险同级。
  • 回滚:单文件、单提交,git revert 即回到 854639b3 的五行原文。

席位意见

(留空)

你要做的

读一遍 :438–442 的五行,确认措辞与你对「同批独立性」和「single-writer」的本意一致;认可深度表出处退回 git 历史;人工合并(受管面,席位不翻 ready、不入队、不挂 auto-merge)。方向 (2)(改检查显示名)与方向 (3)(绿检查输出列出声明集)留给门禁所在车道自行决定。

🤖 Generated with Claude Code

https://claude.ai/code/session_01HxLw5aKDPR5RJgyUR7Exkd


Generated by Claude Code

…numerates, said where the seat reads batch independence

Two lines in the 候选与批次 block, paid in place at 812/812 and ≤120 B per line:
the declared single-writer set is the enumeration in SINGLE_CLAIM_PATHS, sharing
any other path between open PRs is ordinary concurrent work whose cost is one
merge resolution paid by whichever lands second, and batch independence is not
read off a package or a check's name.

Paid by three deletions in the same five lines: the `priority:p0` 可超 `batch`
clause that duplicated the line three below it verbatim; 读数带 UTC 写进认领, which
the UTC-reading rule earlier in the file already states for claims; and the
depth-table provenance of LOCK_DEPTH_HOLD, which now lives in the PR that landed
it (git history), per the file's own one-line-provenance charter.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HxLw5aKDPR5RJgyUR7Exkd
@yinlianghui yinlianghui added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 9, 2026 — with Claude
@github-actions github-actions Bot added size/xs documentation Improvements or additions to documentation labels Sep 9, 2026

Copy link
Copy Markdown
Collaborator Author

维护者速读 — PR #17089(#17032,skills 席终稿,2026-09-09T07:36Z)

改了什么 — 派发席读「同批独立性」的那几行(SKILL.md :438–442)原地改写两行:single-writer 路径只是 scripts/check-single-claim-paths.mjsSINGLE_CLAIM_PATHS 枚举的那几条(今天一条 .objectui-sha);两个 open PR 共享其它任何路径是普通并发,后落地的一方解一次冲突;同批独立按文件面判,⛔ 不按包、不按 check 名。付账:删掉一处 priority:p0 可超 batch 的重复(:441 仍在)、一处「读数带 UTC」的重复(:163 仍在),以及深度表的四个百分比(它是当初定 LOCK_DEPTH_HOLD = 2 的实测出处,规则本身与阈值都还在,出处留在落地它的 PR #16271)。

为什么改 — services 席一小时内三次按「两个 PR 共享的任何文件都是 single-writer 路径」这个不存在的规则行事(阻塞一张卡、给在飞 dev 发围栏、编出一个类别),因为唯一能读到的东西是 check 的名字。dev 按卡面要求先做了扫描:这不是一个席位的个人误读——services、cli、engine、devx 四个派发车道加一个评审席,四天里 ≥12 条评论按同一个错误模型行事,三次各自的纠正都没传到下一个席位。

风险与代价(含回滚) — 纯指令文本;回滚 = revert 本 PR。真实代价一处:深度表的百分比从技能里删掉了——若你希望阈值出处留在文本里,把那半句放回并另删一处即可(dev 在 PR 正文写了替代方案)。改 check 名(卡面方向 2)归门禁所属车道,本 PR 不动脚本。

席位意见 — 接受(契约复审档席内复核)。棘轮 812/151 未动;id-lint、frame-sync 绿;控制字节 0;每行 ≤118 字节;mergeable: true;CI 收敛中。

你要做的 — 合并;或由 os-zhuang / hotlong 批准,本席随即入队。只需看深度表那半句要不要留。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 9, 2026 07:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation needs-user-decision size/xs skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants